<?php

/*
 * Copyright (C) 2009 - 2011 Pham Cong Dinh
 *
 * This file is part of Spica.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

/**
 * Abstraction of the real world user for Spica.
 * This encapsulates the username and the various roles the user can be in.
 *
 * This allows non-security related user information (such as email addresses,
 * telephone numbers etc) to be stored in a convenient location.
 *
 * SpicaUser will be stored in session or caches and as such multiple requests
 * may use the same instance.
 *
 * namespace spica\core\security\User
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      March 25, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
abstract class SpicaUser
{
    /**
     * The username
     *
     * @var string
     */
    public $username;

    /**
     * Active role ID of the user.
     *
     * @var int
     */
    public $roleId;

    /**
     * User is guest.
     *
     * @var bool
     */
    public $isGuest = true;

    /**
     * Constructs a <code>SpicaUser</code> object for the username and the roles of the user.
     *
     * @param string $username
     * @param int    $roleId
     * @param bool   $isGuest
     */
    public function __construct($username, $roleId, $isGuest = true)
    {
        $this->username = $username;
        $this->roleId   = $roleId;
        $this->isGuest  = $isGuest;
    }
}

/**
 * Abstract class indicating the Role of a user. Concrete classes which is
 * derived from this class will provides the various rights for each role.
 *
 * Major concreate classes may be:
 * + AdministratorRole
 * + ApproverRole
 * + ConsumerRole
 * + ContributorRole
 * + EditorRole
 * + FacilitatorRole
 * + PublisherRole
 * + SubmittorRole
 *
 * namespace spica\core\security\Role
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      March 25, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
abstract class SpicaRole
{
    /**
     * The role name set for the SpicaRole
     *
     * @var string
     */
    protected $_roleName;

    /**
     * The role ID set for the SpicaRole
     *
     * @var string
     */
    protected $_roleId;

    /**
     * Constructs a SpicaRole object for a given role name.
     *
     * @param string $roleName
     * @param string $roleId
     */
    public function __construct($roleName, $roleId)
    {
        $this->_roleName = $roleName;
        $this->_roleId   = $roleId;
    }

    /**
     * Returns the role name
     *
     * @return string the role name set for the SpicaRole.
     */
    public abstract function getRoleName();

    /**
     * Indicates whether create operation is supported for users of this role.
     *
     * @return bool indicating whether create operation is supported for users of this role.
     */
    public abstract function isCreationSupported();

    /**
     * Indicates whether delete operation is supported for users of this role.
     *
     * @return bool indicating whether read operation is supported for users of this role.
     */
    public abstract function isDeleteSupported();

    /**
     * Indicates whether edit operation is supported for users of this role.
     *
     * @return bool indicating whether edit operation is supported for users of this role.
     */
    public abstract function isEditSupported();

    /**
     * Indicates whether read operation is supported for users of this role.
     *
     * @return bool indicating whether delete operation is supported for users of this role.
     */
    public abstract function isReadSupported();
}

/**
 * Implements a provider-based approach to authentication decisions.
 *
 * Authentication is the process of obtaining identification credentials from
 * a user (such as name and password ), and validating those credentials against
 * some authority.
 *
 * If the credentials are valid, the entity that submitted the credentials is
 * considered an authenticated identity. Once an identity has been authenticated,
 * the authorization process determines whether that identity has access to a
 * given resource.
 *
 * Spica implements authentication through authentication providers, the modules
 * that contain the code to authenticate the requestor's credentials.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 07, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
interface SpicaAuthenticationProvider
{
    /**
     * Carries out an authentication attempt.
     *
     * @throws SpicaAuthenticationException
     * @param  AuthenticationRequest $authenticationRequest
     */
    public function authenticate($authenticationRequest);

    /**
     * Returns true if this SpicaAuthenticationProvider supports the indicated
     * SpicaAuthenticationRequest object.
     *
     * @return bool
     */
    public function support($authenticationRequest);

    /**
     * Obtains the granted authorities for the specified user.
     *
     * @return SpicaUser
     */
    public function getUserDetails();
}

/**
 * Represents an authentication request.
 *
 * An SpicaAuthenticationRequest object is not considered authenticated until
 * it is processed by a SpicaAuthenticationManager.
 *
 * Stored in a request SpicaSecurityContext.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 07, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
interface SpicaAuthenticationRequest
{
    /**
     * Used to indicate to AbstractSecurityInterceptor whether it should present
     * the authentication token to the AuthenticationManager.
     *
     * @return bool
     */
    public function isAuthenticated();

    /**
     * Implementations should always allow this method to be called with a false
     * parameter, as this is used by various classes to specify the authentication
     * token should not be trusted. If an implementation wishes to reject an
     * invocation with a true parameter (which would indicate the authentication
     * token is trusted - a potential security risk) the implementation should
     * throw an InvalidArgumentException.
     *
     * @param bool $isAuthenticated
     * @return bool
     */
    public function setAuthenticated($isAuthenticated);

    /**
     * Gets the identity of the principal being authenticated, which is usually
     * a username or email.
     *
     * A principal is an abstract notion, which can be used to represent any entity,
     * such as an individual, a corporation, and a login ID.
     *
     * @return string
     */
    public function getPrincipalName();

    /**
     * Gets the credentials that prove the principal is correct. This is usually
     * a password, but could be anything relevant to the AuthenticationManager.
     *
     * @return string the credentials that prove the identity of the principal
     */
    public function getCredentials();
}

/**
 * Forms authentication generally refers to a system in which unauthenticated
 * requests are redirected to an HTML form.
 *
 * Forms authentication is a good choice if your application needs to collect
 * its own user credentials at logon time through HTML forms. The user provides
 * credentials and submits the form. If the application authenticates the request,
 * the system issues a form that contains the credentials or a key for reacquiring
 * the identity. Subsequent requests are issued with the form in the request headers.
 * They are authenticated and authorized by an Spica handler using whatever
 * validation method the application specifies.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 07, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
abstract class SpicaFormAuthenticationProvider implements SpicaAuthenticationProvider
{
    /**
     * Carries out an authentication attempt.
     *
     * @param SpicaAuthenticationRequest $authenticationRequest
     */
    public function authenticate($authenticationRequest)
    {
        throw new BadMethodCallException('This method must be overridden in derived classes. ');
    }

    /**
     * Returns true if this SpicaAuthenticationProvider supports the indicated
     * SpicaAuthenticationRequest object.
     *
     * @param  SpicaAuthenticationRequest $authenticationRequest
     * @return bool
     */
    public function support($authenticationRequest)
    {
        return ($authenticationRequest instanceof SpicaFormAuthenticationRequest);
    }

    /**
     * Obtains the granted authorities for the specified user.
     *
     * @return SpicaUser
     */
    public function getUserDetails()
    {
        throw new BadMethodCallException('This method must be overridden in derived classes. ');
    }
}

/**
 * This class provides services to encapsulate an http server authentication using
 * the "Digest" scheme, as described in RFC2069 and updated in RFC2617.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 07, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaHttpAuthenticationProvider implements SpicaAuthenticationProvider
{
    /**
     * An implementation of SpicaHttpAuthenticator
     *
     * @var SpicaHttpAuthenticator
     */
    protected $_authenticator = array();

    /**
     * Creates a new object of <code>SpicaHttpAuthenticationProvider</code>.
     *
     * @param SpicaHttpAuthenticator $authenticator
     */
    public function __construct($authenticator)
    {
        $this->_authenticator = $authenticator;
    }

    /**
     * Carries out an authentication attempt.
     *
     * @param SpicaDigestAuthenticationRequest $authenticationRequest
     */
    public function authenticate($authenticationRequest)
    {
        $principal = $authenticationRequest->getPrincipalName();
        $password  = $authenticationRequest->getCredentials();

        if ($authenticationRequest instanceof SpicaBasicAuthenticationRequest)
        {
            return ($this->_authenticator->getPassword($principal) === $password);
        }

        $userPassword  = $this->_authenticator->getPassword($principal);
        $realm         = $authenticationRequest->getRealm();
        $uri           = $authenticationRequest->getUri();
        $nonce         = $authenticationRequest->getNonce();
        $cnonce        = $authenticationRequest->getCNonce();
        $nc            = $authenticationRequest->getNc();
        $qop           = $authenticationRequest->getQop();
        $a1            = md5($principal . ':' . $realm . ':' . $userPassword);
        $a2            = md5($_SERVER['REQUEST_METHOD'].':'.$uri);
        $validPassword = md5($a1.':'.$nonce.':'.$nc.':'.$cnonce.':'.$qop.':'.$a2);
        return ($password === $validPassword);
    }

    /**
     * Returns true if this SpicaAuthenticationProvider supports the indicated
     * SpicaAuthenticationRequest object.
     *
     * @param  SpicaAuthenticationRequest $authenticationRequest
     * @return bool
     */
    public function support($authenticationRequest)
    {
        return ($authenticationRequest instanceof SpicaBasicAuthenticationRequest);
    }

    /**
     * Obtains the granted authorities for the specified user.
     *
     * @return SpicaUser
     */
    public function getUserDetails()
    {
        throw new BadMethodCallException('This method must be overridden in derived classes. ');
    }
}

/**
 * An SpicaAuthenticationProvider implementation that provides integration with an LDAP server.
 *
 * There are many ways in which an LDAP directory can be configured so this class
 * delegates most of its responsibilites to two separate strategy interfaces,
 * LdapAuthenticator and LdapAuthoritiesPopulator.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 07, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaLdapAuthenticationProvider implements SpicaAuthenticationProvider
{
    /**
     * List of SpicaLdapAuthenticator(s)
     *
     * @var array
     */
    protected $_authenticators = array();

    /**
     * Creates a new object of <code>SpicaFormAuthenticationRequest</code>.
     *
     * @param array $authenticators List of SpicaLdapAuthenticator(s)
     */
    public function __construct($authenticators)
    {
        $this->_authenticators = $authenticators;
    }

    /**
     * Carries out an authentication attempt.
     *
     * @param SpicaAuthenticationRequest $authenticationRequest
     */
    public function authenticate($authenticationRequest)
    {
        foreach ($this->_authenticators as $authenticator)
        {
            $authenticator->authenticate($authenticationRequest);
        }
    }

    /**
     * Returns true if this SpicaAuthenticationProvider supports the indicated
     * SpicaAuthenticationRequest object.
     *
     * @param  SpicaAuthenticationRequest $authenticationRequest
     * @return bool
     */
    public function support($authenticationRequest)
    {
        return ($authenticationRequest instanceof SpicaLdapAuthenticationRequest);
    }

    /**
     * Obtains the granted authorities for the specified user.
     *
     * @return SpicaUser
     */
    public function getUserDetails()
    {
        throw new BadMethodCallException('This method must be overridden in derived classes. ');
    }
}

/**
 * The strategy interface for locating and authenticating an Ldap user.
 *
 * The LdapAuthenticationProvider calls this interface to authenticate a user
 * and obtain the information for that user from the directory.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 07, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
interface SpicaLdapAuthenticator
{
    /**
     * Authenticates as a user and obtains additional user information from the directory.
     *
     * @param SpicaAuthenticationRequest $authenticationRequest
     */
    public function authenticate($authenticationRequest);
}

/**
 * The strategy interface for authenticating an user via HTTP Digest/Basic Authentication.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 07, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
interface SpicaHttpAuthenticator
{
    /**
     * Gets an user's password.
     *
     * @return string
     */
    public function getPassword($username);
}

/**
 * An SpicaAuthenticationRequest implementation that is designed for
 * simple presentation of a username/email and password.
 *
 * The principal and credentials should be set with a set of $_POST indices
 * that provides the respective property.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 07, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaFormAuthenticationRequest implements SpicaAuthenticationRequest
{
    /**
     * Is the authentication request validated?
     *
     * @var bool
     */
    protected $_isAuthenticated = false;

    /**
     * The map between form element name attribute and request attributes.
     *
     * @var array
     */
    protected $_props = array();

    /**
     * Represents user form that is used in a sign-in action.
     *
     * @var SpicaForm
     */
    protected $_form;

    /**
     * Creates a new object of <code>SpicaFormAuthenticationRequest</code>.
     *
     * @param SpicaForm $form
     * @param array     $props The map between form element name attribute and request attributes.
     */
    public function __construct($form, $props)
    {
        if (false === isset($props['principal']) || false === isset($props['credential']))
        {
            throw new InvalidArgumentException('The two keys "principal" and "credential" must be set.');
        }

        $this->_props = $props;
        $this->_form  = $form;
    }

    /**
     * (non-PHPdoc)
     * @see trunk/library/spica/core/security/SpicaAuthenticationRequest#isAuthenticated()
     */
    public function isAuthenticated()
    {
        return $this->_isAuthenticated;
    }

    /**
     * (non-PHPdoc)
     * @see trunk/library/spica/core/security/SpicaAuthenticationRequest#setAuthenticated()
     */
    public function setAuthenticated($isAuthenticated)
    {
        $this->_isAuthenticated = (bool) $isAuthenticated;
    }

    /**
     * (non-PHPdoc)
     * @see trunk/library/spica/core/security/SpicaAuthenticationRequest#getPrincipalName()
     */
    public function getPrincipalName()
    {
        return $this->_form->get($this->_props['principal']);
    }

    /**
     * (non-PHPdoc)
     * @see trunk/library/spica/core/security/SpicaAuthenticationRequest#getCredentials()
     */
    public function getCredentials()
    {
        return $this->_form->get($this->_props['credential']);
    }

    /**
     * Gets authentication request property value.
     *
     * @param string $name
     */
    public function getProperty($name)
    {
        if (isset($this->_props[$name]))
        {
            return $this->_form->get($this->_props[$name]);
        }

        return $this->_form->get($name);
    }
}

/**
 * An SpicaAuthenticationRequest implementation that is designed for
 * Basic Access Authentication which is defined in RFC 2617.
 * (@see http://tools.ietf.org/html/rfc2617).
 *
 * The "basic" authentication scheme is based on the model that the
 * client must authenticate itself with a user-ID and a password for
 * each realm. {@see http://msdn.microsoft.com/en-us/library/aa479391.aspx}
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 07, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaBasicAuthenticationRequest implements SpicaAuthenticationRequest
{
    /**
     * Is request authenticated?
     *
     * @var bool
     */
    protected $_isAuthenticated = false;

    /**
     * Creates a new object of <code>SpicaBasicAuthenticationRequest</code>.
     */
    public function __construct()
    {

    }

    /**
     * (non-PHPdoc)
     * @see trunk/library/spica/core/security/SpicaAuthenticationRequest#isAuthenticated()
     */
    public function isAuthenticated()
    {
        return $this->_isAuthenticated;
    }

    /**
     * (non-PHPdoc)
     * @see trunk/library/spica/core/security/SpicaAuthenticationRequest#setAuthenticated()
     */
    public function setAuthenticated($isAuthenticated)
    {
        $this->_isAuthenticated = (bool) $isAuthenticated;
    }

    /**
     * (non-PHPdoc)
     * @see trunk/library/spica/core/security/SpicaAuthenticationRequest#getPrincipalName()
     */
    public function getPrincipalName()
    {
        if (isset($_SERVER['PHP_AUTH_USER']))
        {
            return $_SERVER['PHP_AUTH_USER'];
        }

        return null;
    }

    /**
     * (non-PHPdoc)
     * @see trunk/library/spica/core/security/SpicaAuthenticationRequest#getCredentials()
     */
    public function getCredentials()
    {
        if (isset($_SERVER['PHP_AUTH_PW']))
        {
            return $_SERVER['PHP_AUTH_PW'];
        }

        return null;
    }
}

/**
 * An SpicaAuthenticationRequest implementation that is designed for
 * Digest Access Authentication which is defined in RFC 2617.
 * (@see http://tools.ietf.org/html/rfc2617).
 *
 * Like Basic, Digest access authentication verifies that both parties
 * to a communication know a shared secret (a password); unlike Basic,
 * this verification can be done without sending the password in the
 * clear, which is Basic's biggest weakness.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 07, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaDigestAuthenticationRequest extends SpicaBasicAuthenticationRequest
{
    /**
     * HTTP headers.
     *
     * @var array
     */
    protected $_headers = array();

    /**
     * Creates a new object of <code>SpicaDigestAuthenticationRequest</code>.
     */
    public function __construct()
    {
        if (!empty($_SERVER['PHP_AUTH_DIGEST']))
        {
            $headers = $this->parse($_SERVER['PHP_AUTH_DIGEST']);

            if (is_array($headers))
            {
                $this->_headers = $headers;
            }
        }
    }

    /**
     * (non-PHPdoc)
     * @see trunk/library/spica/core/security/SpicaBasicAuthenticationRequest#getPrincipalName()
     */
    public function getPrincipalName()
    {
        if (isset($this->_headers['username']))
        {
            return $this->_headers['username'];
        }

        return null;
    }

    /**
     * (non-PHPdoc)
     * @see trunk/library/spica/core/security/SpicaBasicAuthenticationRequest#getCredentials()
     */
    public function getCredentials()
    {
        if (isset($this->_headers['response']))
        {
            return $this->_headers['response'];
        }

        return null;
    }

    /**
     * Gets URI header in digest string.
     *
     * @return string
     */
    public function getUri()
    {
        if (isset($this->_headers['uri']))
        {
            return $this->_headers['uri'];
        }

        return null;
    }

    /**
     * Gets nonce header in digest string.
     *
     * @return string
     */
    public function getNonce()
    {
        if (isset($this->_headers['nonce']))
        {
            return $this->_headers['nonce'];
        }

        return null;
    }

    /**
     * Gets cnonce header in digest string.
     *
     * @return string
     */
    public function getCNonce()
    {
        if (isset($this->_headers['cnonce']))
        {
            return $this->_headers['cnonce'];
        }

        return null;
    }

    /**
     * Gets realm header in digest string.
     *
     * @return string
     */
    public function getRealm()
    {
        if (isset($this->_headers['realm']))
        {
            return $this->_headers['realm'];
        }

        return null;
    }

    /**
     * Gets qop header in digest string.
     *
     * @return string
     */
    public function getQop()
    {
        if (isset($this->_headers['qop']))
        {
            return $this->_headers['qop'];
        }

        return null;
    }

    /**
     * Gets nc header in digest string.
     *
     * @return string
     */
    public function getNc()
    {
        if (isset($this->_headers['nc']))
        {
            return $this->_headers['nc'];
        }

        return null;
    }

    /**
     * Parses the HTTP digest header.
     *
     * @param  string $digest
     * @return array|false
     */
    public function parse($digest)
    {
        preg_match_all('@(realm|username|nonce|uri|nc|cnonce|qop|response)=[\'"]?([^\'",]+)@', $digest, $matches);
        $data = array_combine($matches[1], $matches[2]);
        // all parts or not?
        return (7 === count($data)) ? $data : false;
    }
}

/**
 * Processes a SpicaAuthenticationRequest.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 07, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaAuthenticationManager
{
    /**
     * List of {@link SpicaAuthenticationProvider}s.
     *
     * @var array
     */
    protected $_providers = array();

    /**
     * Sets the {@link SpicaAuthenticationProvider} objects to be used for authentication.
     *
     * @param array $providers List of SpicaAuthenticationProvider objects
     */
    public function setProviders($providers)
    {
        $this->_providers = $providers;
    }

    /**
     * Adds a {@link SpicaAuthenticationProvider} object to be used for authentication.
     *
     * @param SpicaAuthenticationProvider $provider SpicaAuthenticationProvider object
     */
    public function addProvider(SpicaAuthenticationProvider $provider)
    {
        $this->_providers[] = $provider;
    }

    /**
     * Attempts to authenticate the passed Authentication object.
     *
     * This method will iterates an {@link SpicaAuthenticationRequest} through a
     * list of {@link SpicaAuthenticationProvider}s
     *
     * @throws SpicaAuthenticationException thrown if an authentication request is rejected
     * @param  SpicaAuthenticationRequest $authenticationRequest
     */
    public function authenticate($authenticationRequest)
    {
        foreach ($this->_providers as $provider)
        {
            if (false === $provider->support($authenticationRequest))
            {
                throw new SpicaProviderNotFoundException('The class '.get_class($authenticationRequest).' is not supported by '.get_class($provider));
            }

            $provider->authenticate($authenticationRequest);
        }
    }
}

/**
 * This class provides the minimum security information associated with the
 * current HTTP request.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 07, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaSecurityContext
{
    /**
     * Constructs an object of <code>SpicaSecurityContext</code>.
     *
     * @throws RuntimeException if a session is not started
     * @param  SpicaUser $user
     */
    public function  __construct(SpicaUser $user)
    {
        if (false === SpicaSession::isStarted())
        {
            throw new RuntimeException('Failed to initialize security context because a session is not started.');
        }

        $_SESSION['__security__']['user'] = $user;
    }

    /**
     * Obtains the currently authenticated principal.
     *
     * @return SpicaUser returns the SpicaUser or null if no authentication information is available
     */
    public function getUser()
    {
        if (isset($_SESSION['__security__']['user']))
        {
            return $_SESSION['__security__']['user'];
        }

        return null;
    }

    /**
     * Changes the currently authenticated principal, or removes the authentication information.
     *
     * @param SpicaUser $user
     */
    public function setUser(SpicaUser $user)
    {
        $_SESSION['__security__']['user'] = $user;
    }

    /**
     * Gets a value indicating whether the request has been authenticated.
     *
     * @return bool
     */
    public static function isAuthenticated()
    {
        return isset($_SESSION['__security__']['user']);
    }

    /**
     * Gets a value indicating whether the user account is identified as a
     * Guest account by the system.
     *
     * @return bool|null null if no authentication information is available
     */
    public function isGuest()
    {
        if (isset($_SESSION['__security__']['user']))
        {
            return $_SESSION['__security__']['user']->isGuest;
        }

        return null;
    }
}

/**
 * The SpicaAuthorizationProvider interface exposes the services provided by an
 * Authorization provider to the Spica Security Framework.
 *
 * This allows the Authorization provider to be manipulated
 * (initialized, check permission).
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 14, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
interface SpicaAuthorizationProvider
{
    /**
     * Starts the Authorization provider.
     *
     * @return bool
     */
    public function initialize();

    /**
     * Carries out an authorization attempt.
     *
     * @throws RuntimeException when there is a problem with the system
     *         such as un-initialized session
     * @throws SpicaAuthorizationException if an authorization is rejected
     * @param  SpicaSecurityContext $securityContext Can be null
     */
    public function accept($securityContext = null);
}

/**
 * The SpicaAuthorizationManager provides all the authorization methods
 * and services offered by the Spica Security Framework.
 *
 * The SpicaAuthorizationManager manages a list of SpicaAuthorizationProvider
 * objects, and tries them one at a time and returns true when all the
 * SpicaAuthorizationProvider(s) authorize the security context.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 14, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaAuthorizationManager
{
    /**
     * List of {@link SpicaAuthorizationProvider}s.
     *
     * @var array
     */
    protected $_providers = array();

    /**
     * Sets the {@link SpicaAuthorizationProvider} objects to be used for authentication.
     *
     * @param array $providers List of SpicaAuthorizationProvider objects
     */
    public function setProviders($providers)
    {
        $this->_providers = $providers;
    }

    /**
     * Adds a {@link SpicaAuthorizationProvider} object to be used for authentication.
     *
     * @param SpicaAuthorizationProvider $provider SpicaAuthorizationProvider object
     */
    public function addProvider(SpicaAuthenticationProvider $provider)
    {
        $this->_providers[] = $provider;
    }

    /**
     * Attempts to authorize the passed SpicaSecurityContext object.
     *
     * This method will iterates an {@link SpicaSecurityContext} through a
     * list of {@link SpicaAuthorizationProvider}s
     *
     * @throws SpicaAuthorizationException if an authorization is rejected
     * @param  SpicaSecurityContext $securityContext
     */
    public function checkPermission($securityContext)
    {
        foreach ($this->_providers as $provider)
        {
            $provider->initialize();
            $provider->accept($securityContext);
        }
    }
}

/**
 * SpicaRoleAuthorizationProvider provides a mechanism to authorize an user with
 * a specific role to access a protected resource.
 *
 * Role authorization can be maintained by the database (using passwords),
 * by the operating system, or by a network service.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 14, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
abstract class SpicaRoleAuthorizationProvider implements SpicaAuthorizationProvider
{
    /**
     * Starts the Authorization provider.
     *
     * In a real implementation, this method can be used to initialize database
     * connection, resource access.
     *
     * @return bool
     */
    public function initialize()
    {
        // no-op by default.
    }

    /**
     * Carries out an authorization attempt.
     *
     * @throws RuntimeException when session is not started
     * @throws SpicaAuthorizationException if an authorization is rejected
     * @param  SpicaSecurityContext $securityContext Can be null
     */
    public function accept($securityContext = null)
    {
        // Security context is not initialized yet. User is not authenticated
        if (null === $securityContext)
        {
            return $this->_allowUnauthenticated();
        }

        $user = $securityContext->getUser();

        if (null === $user)
        {
            return $this->_allowUnauthenticated(null, $securityContext);
        }
        else if (true === $user->isGuest)
        {
            return $this->_allowGuest($user);
        }

        return $this->_allowRole($user->roleId);
    }

    /**
     * Processes authorization when user is not authenticated.
     *
     * @throws SpicaAuthorizationException if authorization failed
     * @param  SpicaUser $user User to be authorized. It can be null
     * @param  SpicaSecurityContext $securityContext Can be null
     */
    public abstract function _allowUnauthenticated($user = null, $securityContext = null);

    /**
     * Processes authorization when user is authenticated as guest. This method
     * nomally checks if all users are allowed to access a page or not.
     *
     * @throws SpicaAuthorizationException if authorization failed
     * @param  SpicaUser $user
     */
    public abstract function _allowGuest($user);

    /**
     * Processes authorization when user is authenticated and assigned a specific role.
     *
     * @throws SpicaAuthorizationException if authorization failed
     * @param  int $roleId
     */
    public abstract function _allowRole($roleId);
}

/**
 * Exception is thrown when user cannot be found.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaUserNotFoundException extends SpicaException {}

/**
 * Exception is thrown if the given user has been banned.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaBannedUserException extends SpicaException
{
    /**
     * Constant that indicates that user is banned permanently.
     *
     * @var int
     */
    const PERMANENT = 1;

    /**
     * Constant that indicates that user is banned temporarily.
     *
     * @var int
     */
    const TEMPORARY = 2;

    /**
     * Date string.
     *
     * @var string
     */
    protected $_expiredDate;

    /**
     * Exception code. Defaults to 1, means user is banned permanently.
     *
     * @var int
     */
    protected $code = 1;

    /**
     * Exception message.
     *
     * @var string
     */
    protected $message = 'Account is banned';   //

    /**
     * Exception code. Defaults to 1, means user is banned permanently.
     *
     * @param string $message
     * @param int    $code
     * @param string $expiredDate
     */
    public function __construct($message = null, $code = 1, $expiredDate = null)
    {
        $this->code         = $code;
        $this->_expiredDate = $expiredDate;

        if (null !== $message)
        {
            $this->message  = $message;
        }
    }
}

/**
 * Root exception for all security-related exceptions.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaSecurityException extends SpicaException {}

/**
 * General exception thrown due to an error during the authentication process.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaAuthenticationException extends SpicaSecurityException {}

/**
 * Exception thrown if there is a problem during authorization.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaAuthorizationException extends SpicaSecurityException {}

/**
 * Exception thrown to indicate a requested operation or access to a
 * requested resource is not allowed.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaUnauthorizedException extends SpicaAuthorizationException {}

/**
 * Exception thrown when a particular client (that is, host address)
 * has not been enabled to access the system or if the client has been
 * enabled access but is not permitted to perform a particluar operation or
 * access a particular resource.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaHostUnauthorizedException extends SpicaAuthorizationException {}

/**
 * Thrown by SpicaAuthenticationManager if no SpicaAuthenticationProvider could
 * be found that supports the presented SpicaAuthenticationRequest object.
 *
 * Authorizations can only be performed after a successful authentication because
 * authorization data (roles, permissions, etc) must always be associated with a
 * known identity. Such a known identity can only be obtained upon a successful log-in.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 12, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaProviderNotFoundException extends SpicaAuthenticationException {}

/**
 * Exception thrown when attempting to execute an authorization action when a
 * successful authentication hasn't yet occurred.
 *
 * Authorizations can only be performed after a successful authentication because
 * authorization data (roles, permissions, etc) must always be associated with a
 * known identity. Such a known identity can only be obtained upon a successful log-in.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaUnauthenticatedException extends SpicaAuthenticationException {}

/**
 * Exception is thrown if the given user is not logged on when a system admin
 * try to log a user off the system.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaUserNotLoggedOnException extends SpicaAuthenticationException {}

/**
 * Exception thrown due to a problem with the credential(s) submitted for
 * an account during the authentication process.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaCredentialException extends SpicaAuthenticationException {}

/**
 * Thrown during the authentication process when the system determines the
 * submitted credential has expired and will not allow login.
 *
 * This is most often used to alert a user that their credential (e.g. password
 * or cryptography key) has expired and they should change its value.
 * In such systems, the component invoking the authentication might catch
 * this exception and redirect the user to an appropriate view to allow them
 * to update their password.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaExpiredCredentialException extends SpicaAuthenticationException {}

/**
 * Exception thrown due to a problem with the credential(s) submitted for
 * an account during the authentication process.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaIncorrectCredentialException extends SpicaAuthenticationException {}

/**
 * Exception thrown due to a problem with the account under which
 * an authentication attempt is being executed.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaAccountException extends SpicaAuthenticationException {}

/**
 * Exception thrown attempting to authenticate and the corresponding
 * account has not been activated.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaAccountNotActivatedException extends SpicaAccountException {}

/**
 * Exception thrown when attempting to authenticate and the corresponding
 * account has been disabled for some reasons.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaDisabledAccountException extends SpicaAccountException {}

/**
 * A special kind of SpicaDisabledAccountException, this exception is thrown when
 * attempting to authenticate and the corresponding account has been disabled
 * explicitly due to being locked.
 *
 * For example, an account can be locked if an administrator explicitly locks
 * an account or perhaps an account can be locked automatically by the system
 * if too many unsuccessful authentication attempts take place during a specific
 * period of time (perhaps indicating a hacking attempt).
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaLockedAccountException extends SpicaDisabledAccountException {}

/**
 * Exception thrown when attempting to authenticate with a principal that
 * doesn't exist in the system (e.g. by specifying a username that doesn't
 * relate to a user account).
 *
 * Whether or not an application wishes to alert a user logging in to the system
 * of this fact is at the discretion of those responsible for designing the view
 * and what happens when this exception occurs.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaUnknownAccountException extends SpicaAccountException {}

/**
 * Exception thrown when a system is configured to only allow a certain number
 * of authentication attempts over a period of time and the current session has
 * failed to authenticate successfully within that number. The resulting action
 * of such an exception is applicaiton dependent, but most systems either
 * temporarily or permanently lock that account to prevent further attempts.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaExcessiveAttemptsException extends SpicaAuthenticationException {}

/**
 * Exception thrown when an authentication attempt has been received for an account
 * that has already been authenticated (i.e. logged-in), and the system is
 * configured to prevent such concurrent access.
 *
 * This is useful when an application must ensure that only one person is
 * logged-in to a single account at any given time.
 *
 * Sometimes account names and passwords are lazily given away to many people
 * for easy access to a system. Such behavior is undesirable in systems where
 * users are accountable for their actions, such as in government applications,
 * or when licensing agreements must be maintained, such as those which only
 * allow 1 user per paid license.
 *
 * By disallowing concurrent access, such systems can ensure that each authenticated
 * session corresponds to one and only one user at any given time.
 *
 * @category   spica
 * @package    core
 * @subpackage security
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 03, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Security.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaConcurrentAccessException extends SpicaAccountException {}

?>